/*
 * Copyright (c) 2011-2013, fortiss GmbH.
 * Licensed under the Apache License, Version 2.0.
 *
 * Use, modification and distribution are subject to the terms specified
 * in the accompanying license file LICENSE.txt located at the root directory
 * of this software distribution. A copy is available at
 * http://chromosome.fortiss.org/.
 *
 * This file is part of CHROMOSOME.
 *
 * $Id: udpReceive.c 7664 2014-03-04 08:47:41Z geisinger $
 */

/**
 * \file
 *
 * \brief  Waypoint that reads the data from UDP network transportation.
 *
 * \author
 *         This file has been generated by the CHROMOSOME Modeling Tool (fortiss GmbH).
 */

/******************************************************************************/
/***   Includes                                                             ***/
/******************************************************************************/
#include "xme/wp/udp/include/udpReceive.h"
#include "xme/com/packet.h"

#include "xme/hal/include/mem.h"

#include <inttypes.h>

/******************************************************************************/
/***   Type definitions                                                     ***/
/******************************************************************************/
/**
 * \struct udpRecvConfigItem_t
 *
 * \brief  Structure for storing a configuration for this waypoint.
 */
typedef struct
{
    xme_core_dataManager_dataPacketId_t dataId; ///< Port to write the data to, which is received on the UDP socket.
    uint16_t sizeOfData; ///< Size of the data.
    uint16_t sizeOfTopicAndAttributes; ///< Size of topic data plus size for all attributes.
    xme_core_topic_t topic; ///< Topic associated to this port.
    void *buffer; ///< Buffer passed for reading the data from the network.
    void *key; ///< The key which is added in the packet header to correctly identify the intended receipent.
    uint16_t port; ///< Port on the given hostname for the connection.
    xme_hal_net_socketHandle_t socketHandle; ///< The opened socket descriptor where data is received.
} udpRecvConfigItem_t;

/**
 * \struct udpRecvSocketConnection_t
 *
 * \brief  Structure for storing a list of open ports and corresponding sockets
 *         This functionality of maintaining separate socket table helps in two ways:
 *         1. If we remove all the configurations then we lose the socket descriptions too.
 *            So any addition of waypoints after removing all will be a failure for OS will 
 *            not allow to open another socket for the same port.
 *         2. This allows us to specify multiple ports for one chromosome instance.
 *         3. count is uint8_t, it can only keep the count for 255 connections.
 *            But it is just being used for statistical purpose, an overflow is not
 *            a cause of worry. Only it will give a wrong value.
 */
typedef struct
{
    uint16_t port; ///< Port on the given hostname for the connection.
    xme_hal_net_socketHandle_t socketHandle; ///< The opened socket descriptor where data is received.
    uint8_t count; ///< count of Receive Configurations using this socketHandle
} udpRecvSocketConnection_t;

/******************************************************************************/
/***   Variables                                                            ***/
/******************************************************************************/
/**
 * \struct xme_wp_udp_udpRecvConfigTable
 * \brief  Table for storing this waypoints configurations.
 */
static struct
{
    XME_HAL_TABLE( udpRecvConfigItem_t, table, XME_WP_UDP_RECEIVE_CONFIGURATIONTABLE_SIZE); ///< Table to store the waypoint configuration
    xme_hal_sync_criticalSectionHandle_t criticalSectionHandle; ///< Critical section handle for protecting critical regions.
} xme_wp_udp_udpRecvConfigTable;

/**
 * \struct xme_wp_udp_udpRecvSocketConnection_table
 * \brief  Table for storing open Sockets for the ports
 */
static XME_HAL_TABLE(udpRecvSocketConnection_t, xme_wp_udp_udpRecvSocketConnection_table, XME_WP_UDP_RECEIVE_CONFIGURATIONTABLE_SIZE);

static xme_hal_table_rowHandle_t udpRecvIndex = XME_HAL_TABLE_INVALID_ROW_HANDLE; ///< index to the entry in config table with maximum buffer size
          ///< This will not work in multiple instances/multi threaded approach

/******************************************************************************/
/***   Implementation                                                       ***/
/******************************************************************************/
xme_status_t
xme_wp_udp_udpReceive_init(void)
{
    XME_ASSERT(XME_ASSERT_NO_SIDE_EFFECTS(0 == XME_HAL_TABLE_ITEM_COUNT(xme_wp_udp_udpRecvConfigTable.table)));
    XME_ASSERT(XME_HAL_SYNC_INVALID_CRITICAL_SECTION_HANDLE == xme_wp_udp_udpRecvConfigTable.criticalSectionHandle);
    
    XME_HAL_TABLE_INIT(xme_wp_udp_udpRecvConfigTable.table);
    XME_HAL_TABLE_INIT(xme_wp_udp_udpRecvSocketConnection_table);

    XME_CHECK
    (
        XME_HAL_SYNC_INVALID_CRITICAL_SECTION_HANDLE != (xme_wp_udp_udpRecvConfigTable.criticalSectionHandle = xme_hal_sync_createCriticalSection()),
        XME_STATUS_OUT_OF_RESOURCES
    );

    udpRecvIndex = XME_HAL_TABLE_INVALID_ROW_HANDLE;

    return XME_STATUS_SUCCESS;
}

xme_status_t
xme_wp_udp_udpReceive_run
(
    xme_wp_waypoint_instanceId_t instanceId
)
{
    xme_status_t status;
    udpRecvConfigItem_t *configurationItem;
    udpRecvConfigItem_t *configurationItemMaxBuffer;
    xme_hal_table_rowHandle_t configItemHandle = XME_HAL_TABLE_INVALID_ROW_HANDLE;
    uint16_t sampleSize;
    void* samplePayload;
    xme_com_packet_sample_header_t* sampleHeader;

    xme_hal_sync_enterCriticalSection(xme_wp_udp_udpRecvConfigTable.criticalSectionHandle);
    configurationItemMaxBuffer = XME_HAL_TABLE_ITEM_FROM_HANDLE(xme_wp_udp_udpRecvConfigTable.table, udpRecvIndex);
    configurationItem = XME_HAL_TABLE_ITEM_FROM_HANDLE(xme_wp_udp_udpRecvConfigTable.table, (xme_hal_table_rowHandle_t)instanceId);
    xme_hal_sync_leaveCriticalSection(xme_wp_udp_udpRecvConfigTable.criticalSectionHandle);


    XME_CHECK
    (
        (NULL != configurationItemMaxBuffer && NULL != configurationItem),
        XME_STATUS_INVALID_HANDLE
    );

    // Read the data on the network and check if it contains valid packet header
    sampleSize = xme_hal_net_readSocket
    (
        configurationItem->socketHandle,
        configurationItemMaxBuffer->buffer,
        (configurationItemMaxBuffer->sizeOfTopicAndAttributes + (uint16_t) sizeof(xme_com_packet_sample_header_t))
    );
    XME_CHECK
    (
        sampleSize > (uint16_t)sizeof(xme_com_packet_sample_header_t),
        XME_STATUS_INTERNAL_ERROR
    );

    //Get the header from the packet
    sampleHeader = (xme_com_packet_sample_header_t*)(configurationItemMaxBuffer->buffer);
    if (!XME_COM_PACKET_VALID(*sampleHeader))
    {
        XME_LOG
        (
            XME_LOG_DEBUG, "Discarding packet with invalid header (magic=0x%02X, ver=0x%02X, type=0x%02X)\n",
            sampleHeader->packetHeader.magic, sampleHeader->packetHeader.version, sampleHeader->packetHeader.packetType
        );

        return XME_STATUS_INTERNAL_ERROR;
    }

    if (xme_hal_mem_compare((sampleHeader->key),configurationItem->key, XME_WP_UDP_HEADER_KEY_LENGTH)!=0)
    {
        //OK we were expecting data for the given instanceId but seems like the data for some other key
        //came first.
        //Remember we have currently two ports, one for BROADCAST listening and other regular
        //So iterate over the table to locate the right configItem
        //And if it still does not exist then we flag an error
        udpRecvConfigItem_t* configurationItemTemp = NULL;

        XME_HAL_TABLE_GET_NEXT(xme_wp_udp_udpRecvConfigTable.table,
                                    xme_hal_table_rowHandle_t, configItemHandle,
                                    udpRecvConfigItem_t, configurationItemTemp,
                                    (xme_hal_mem_compare((sampleHeader->key),configurationItemTemp->key, XME_WP_UDP_HEADER_KEY_LENGTH)==0));
    
        if (XME_HAL_TABLE_INVALID_ROW_HANDLE == configItemHandle)
        {
            uint8_t i;

            XME_LOG
            (
                XME_LOG_DEBUG, "Discarding packet with invalid key (magic=0x%02X, ver=0x%02X, type=0x%02X key=",
                sampleHeader->packetHeader.magic, sampleHeader->packetHeader.version, sampleHeader->packetHeader.packetType
            );
            for(i = 0;i < XME_WP_UDP_HEADER_KEY_LENGTH; i++)
                XME_LOG(XME_LOG_DEBUG, "%" PRIu8 ,sampleHeader->key[i]);
            XME_LOG(XME_LOG_DEBUG, "\n");

            return XME_STATUS_INTERNAL_ERROR;
        }

        configurationItem = configurationItemTemp;

        XME_CHECK
        (
            NULL != configurationItem,
            XME_STATUS_INVALID_HANDLE
        );
    }

    //Get the paylod from the packet
    samplePayload = XME_COM_PACKET_SAMPLE_PAYLOAD( configurationItemMaxBuffer->buffer );

    // Indicate we are going to start the write operation.
    status = xme_core_dataHandler_startWriteOperation(configurationItem->dataId);
    XME_CHECK(XME_STATUS_SUCCESS == status, XME_STATUS_INTERNAL_ERROR);

    // And copy it out to the dataId of the found configuration
    status = xme_core_dataHandler_writeData(configurationItem->dataId,
                                            samplePayload,
                                            configurationItem->sizeOfData);

    XME_CHECK(XME_STATUS_SUCCESS == status, XME_STATUS_INTERNAL_ERROR);

    // Get all attributes from payload and write them to the output port
    {
        xme_core_attribute_descriptor_list_t attributeDescriptorList;
        uint8_t i;
        uint8_t* payloadPtr = ((uint8_t*)samplePayload) + configurationItem->sizeOfData;

        status = xme_core_directory_attribute_getAttributeDescriptorList(configurationItem->topic, &attributeDescriptorList);

        if (XME_STATUS_SUCCESS == status) // When there is no attribute desciptor list for the given topic, then simply do nothing
        {
            for (i = 0; i < attributeDescriptorList.length; i++)
            {
                status = xme_core_dataHandler_writeAttribute
                (
                    configurationItem->dataId,
                    attributeDescriptorList.element[i].key,
                    payloadPtr,
                    attributeDescriptorList.element[i].size
                );

                payloadPtr += attributeDescriptorList.element[i].size;
                
                XME_ASSERT(status == XME_STATUS_SUCCESS);

                if (status != XME_STATUS_SUCCESS)
                {
                    XME_LOG(XME_LOG_WARNING, "Writing of attribute '%" PRIu32 "' failed with status %" PRIu32 ".", attributeDescriptorList.element[i].key, status);
                }
            }
        }
    }

    status = xme_core_dataHandler_completeWriteOperation(configurationItem->dataId);
    XME_CHECK(XME_STATUS_SUCCESS == status, XME_STATUS_INTERNAL_ERROR);

    return XME_STATUS_SUCCESS;
}

xme_status_t
xme_wp_udp_udpReceive_getConfig
(
    xme_wp_waypoint_instanceId_t* instanceId,
    xme_core_dataManager_dataPacketId_t* dataId,
    const void *key,
    uint16_t port
)
{
    xme_hal_table_rowHandle_t exists=XME_HAL_TABLE_INVALID_ROW_HANDLE;
    udpRecvConfigItem_t* existsItem=NULL;

    XME_HAL_TABLE_GET_NEXT(xme_wp_udp_udpRecvConfigTable.table,
                                    xme_hal_table_rowHandle_t, exists,
                                    udpRecvConfigItem_t, existsItem,
                                    (existsItem->port==port && xme_hal_mem_compare(existsItem->key,key,XME_WP_UDP_HEADER_KEY_LENGTH)==0));
    if (XME_HAL_TABLE_INVALID_ROW_HANDLE!=exists)
    {
        XME_CHECK(NULL != existsItem, XME_STATUS_INTERNAL_ERROR);

        //So his already exists, we dont have to add another entry we can return the same instance ID
        *instanceId = (xme_wp_waypoint_instanceId_t)exists;
        *dataId = existsItem->dataId;
        return XME_STATUS_SUCCESS;
    }
    return XME_STATUS_NOT_FOUND;
}

xme_status_t
xme_wp_udp_udpReceive_addConfig
(
    xme_wp_waypoint_instanceId_t* instanceId,
    xme_core_dataManager_dataPacketId_t dataId,
    xme_core_topic_t topic,
    uint16_t sizeOfData,
    void *buffer,
    uint16_t sizeOfBuffer,
    const void *key,
    uint16_t port
)
{
    xme_hal_table_rowHandle_t configurationItemHandle, socketConnectionRH=XME_HAL_TABLE_INVALID_ROW_HANDLE;
    udpRecvConfigItem_t* configurationItem; 
    udpRecvSocketConnection_t* socketConnectionItem=NULL;
    uint16_t sizeOfTopicAndAttributes;
    uint8_t i;
    xme_core_attribute_descriptor_list_t attributeDescriptorList;
    xme_status_t status;

    status = xme_core_directory_attribute_getAttributeDescriptorList(topic, &attributeDescriptorList);

    sizeOfTopicAndAttributes = sizeOfData;

    if (XME_STATUS_SUCCESS == status) // When there is no attribute desciptor list for the given topic, then simply do nothing
    {
        for (i = 0; i < attributeDescriptorList.length; i++)
        {
            sizeOfTopicAndAttributes += attributeDescriptorList.element[i].size;
        }
    }

    XME_CHECK
    (
        sizeOfBuffer >= (sizeOfTopicAndAttributes + (uint16_t)sizeof(xme_com_packet_sample_header_t)),
        XME_STATUS_INVALID_PARAMETER
    );

    {
        xme_hal_table_rowHandle_t handle = XME_HAL_TABLE_INVALID_ROW_HANDLE;
        udpRecvConfigItem_t* configItem = NULL;
        XME_HAL_TABLE_GET_NEXT(xme_wp_udp_udpRecvConfigTable.table,
                                    xme_hal_table_rowHandle_t, handle,
                                    udpRecvConfigItem_t, configItem,
                                    (configItem->port==port && xme_hal_mem_compare(configItem->key,key,XME_WP_UDP_HEADER_KEY_LENGTH)==0));
        if (XME_HAL_TABLE_INVALID_ROW_HANDLE!=handle)
        {
            //So his already exists, we dont have to add another entry we can return the same instance ID
            *instanceId = (xme_wp_waypoint_instanceId_t)handle;
            return XME_STATUS_SUCCESS;
        }
    }
    xme_hal_sync_enterCriticalSection(xme_wp_udp_udpRecvConfigTable.criticalSectionHandle);
    configurationItemHandle = XME_HAL_TABLE_ADD_ITEM(xme_wp_udp_udpRecvConfigTable.table);
    xme_hal_sync_leaveCriticalSection(xme_wp_udp_udpRecvConfigTable.criticalSectionHandle);

    XME_CHECK
    (
        XME_HAL_TABLE_INVALID_ROW_HANDLE != configurationItemHandle, 
        XME_STATUS_OUT_OF_RESOURCES
    );
    *instanceId = (xme_wp_waypoint_instanceId_t)configurationItemHandle;

    configurationItem = XME_HAL_TABLE_ITEM_FROM_HANDLE
    (
        xme_wp_udp_udpRecvConfigTable.table, 
        configurationItemHandle
    );
    
    if (NULL == configurationItem)
    {
        return XME_STATUS_INTERNAL_ERROR;
    }

    configurationItem->key = (void *)xme_hal_mem_alloc(XME_WP_UDP_HEADER_KEY_LENGTH);
    (void) xme_hal_mem_copy(configurationItem->key,key,XME_WP_UDP_HEADER_KEY_LENGTH);
    configurationItem->port = port;
    configurationItem->dataId = dataId;
    configurationItem->sizeOfData = sizeOfData;
    configurationItem->sizeOfTopicAndAttributes = sizeOfTopicAndAttributes;
    configurationItem->topic = topic;
    configurationItem->buffer = buffer;

    XME_HAL_TABLE_GET_NEXT
    (
        xme_wp_udp_udpRecvSocketConnection_table,
        xme_hal_table_rowHandle_t, socketConnectionRH,
        udpRecvSocketConnection_t, socketConnectionItem,
        socketConnectionItem->port == port
    );
    if (XME_HAL_TABLE_INVALID_ROW_HANDLE == socketConnectionRH)
    {
        socketConnectionRH = XME_HAL_TABLE_ADD_ITEM(xme_wp_udp_udpRecvSocketConnection_table);
        XME_CHECK_REC
        (
            XME_HAL_TABLE_INVALID_ROW_HANDLE != socketConnectionRH, 
            XME_STATUS_OUT_OF_RESOURCES,
            {
                xme_hal_sync_enterCriticalSection(xme_wp_udp_udpRecvConfigTable.criticalSectionHandle);
                XME_CHECK(XME_STATUS_SUCCESS == XME_HAL_TABLE_REMOVE_ITEM(xme_wp_udp_udpRecvConfigTable.table, configurationItemHandle), XME_STATUS_INTERNAL_ERROR);
                xme_hal_sync_leaveCriticalSection(xme_wp_udp_udpRecvConfigTable.criticalSectionHandle);
            }
        );
        socketConnectionItem = XME_HAL_TABLE_ITEM_FROM_HANDLE
        (
            xme_wp_udp_udpRecvSocketConnection_table,
            socketConnectionRH
        );

        socketConnectionItem->socketHandle = xme_hal_net_createSocket(NULL, XME_HAL_NET_SOCKET_UDP | XME_HAL_NET_SOCKET_NONBLOCKING, NULL, port);

        XME_CHECK_REC
        (
            (XME_HAL_NET_INVALID_SOCKET_HANDLE != socketConnectionItem->socketHandle),
            XME_STATUS_OUT_OF_RESOURCES,
            {
                xme_hal_sync_enterCriticalSection(xme_wp_udp_udpRecvConfigTable.criticalSectionHandle);
                XME_CHECK(XME_STATUS_SUCCESS == XME_HAL_TABLE_REMOVE_ITEM(xme_wp_udp_udpRecvConfigTable.table, configurationItemHandle), XME_STATUS_INTERNAL_ERROR);
                xme_hal_sync_leaveCriticalSection(xme_wp_udp_udpRecvConfigTable.criticalSectionHandle);
            }
        );

        status = xme_hal_net_openSocket( socketConnectionItem->socketHandle );
        XME_CHECK_REC
        (
            (XME_STATUS_SUCCESS == status),
            XME_STATUS_OUT_OF_RESOURCES,
            {
                XME_CHECK(XME_STATUS_SUCCESS == xme_hal_net_destroySocket(socketConnectionItem->socketHandle), XME_STATUS_INTERNAL_ERROR);
                xme_hal_sync_enterCriticalSection(xme_wp_udp_udpRecvConfigTable.criticalSectionHandle);
                XME_CHECK(XME_STATUS_SUCCESS == XME_HAL_TABLE_REMOVE_ITEM(xme_wp_udp_udpRecvConfigTable.table, configurationItemHandle), XME_STATUS_INTERNAL_ERROR);
                xme_hal_sync_leaveCriticalSection(xme_wp_udp_udpRecvConfigTable.criticalSectionHandle);
            }
        );
        socketConnectionItem->port = port;
        socketConnectionItem->count = 0;
    }
    XME_ASSERT(NULL != socketConnectionItem);
    socketConnectionItem->count++;
    configurationItem->socketHandle = socketConnectionItem->socketHandle;

    if (XME_HAL_TABLE_INVALID_ROW_HANDLE != udpRecvIndex)
    {
        xme_hal_sync_enterCriticalSection(xme_wp_udp_udpRecvConfigTable.criticalSectionHandle);
        configurationItem = XME_HAL_TABLE_ITEM_FROM_HANDLE(xme_wp_udp_udpRecvConfigTable.table, udpRecvIndex);
        xme_hal_sync_leaveCriticalSection(xme_wp_udp_udpRecvConfigTable.criticalSectionHandle);
        if (sizeOfTopicAndAttributes > configurationItem->sizeOfTopicAndAttributes)
            udpRecvIndex = configurationItemHandle;
    }
    else
        udpRecvIndex = configurationItemHandle;

    return XME_STATUS_SUCCESS;
}

xme_status_t
xme_wp_udp_udpReceive_removeConfig
(
    xme_wp_waypoint_instanceId_t instanceId
)
{
    xme_status_t status;
    udpRecvConfigItem_t* configurationItem = NULL;
    udpRecvSocketConnection_t* socketConnectionItem = NULL;
    xme_hal_table_rowHandle_t socketConnectionRH = XME_HAL_TABLE_INVALID_ROW_HANDLE;

    XME_CHECK(XME_WP_WAYPOINT_INSTANCEID_INVALID != instanceId, XME_STATUS_INVALID_HANDLE);

    xme_hal_sync_enterCriticalSection(xme_wp_udp_udpRecvConfigTable.criticalSectionHandle);
    configurationItem = XME_HAL_TABLE_ITEM_FROM_HANDLE(xme_wp_udp_udpRecvConfigTable.table, (xme_hal_table_rowHandle_t)instanceId);
    xme_hal_sync_leaveCriticalSection(xme_wp_udp_udpRecvConfigTable.criticalSectionHandle);

    XME_CHECK(NULL != configurationItem, XME_STATUS_INVALID_HANDLE);

    xme_hal_mem_free(configurationItem->key);

    XME_HAL_TABLE_GET_NEXT
    (
        xme_wp_udp_udpRecvSocketConnection_table,
        xme_hal_table_rowHandle_t, socketConnectionRH,
        udpRecvSocketConnection_t, socketConnectionItem,
        socketConnectionItem->port == configurationItem->port
    );
    if (NULL != socketConnectionItem)
    {
        XME_ASSERT(0 < socketConnectionItem->count);
        socketConnectionItem->count--;
    }

    xme_hal_sync_enterCriticalSection(xme_wp_udp_udpRecvConfigTable.criticalSectionHandle);
    status = XME_HAL_TABLE_REMOVE_ITEM(xme_wp_udp_udpRecvConfigTable.table, (xme_hal_table_rowHandle_t)(instanceId));
    xme_hal_sync_leaveCriticalSection(xme_wp_udp_udpRecvConfigTable.criticalSectionHandle);

    XME_ASSERT(XME_STATUS_SUCCESS == status || XME_STATUS_INVALID_HANDLE == status);

    return status;
}

xme_status_t
xme_wp_udp_udpReceive_fini(void)
{
    XME_HAL_TABLE_ITERATE_BEGIN
    (
        xme_wp_udp_udpRecvConfigTable.table,
        xme_hal_table_rowHandle_t, configurationItemHandle,
        udpRecvConfigItem_t, configurationItem
    );
    {
        xme_hal_mem_free(configurationItem->key);
    }
    XME_HAL_TABLE_ITERATE_END();

    XME_HAL_TABLE_ITERATE_BEGIN
    (
        xme_wp_udp_udpRecvSocketConnection_table,
        xme_hal_table_rowHandle_t, configurationItemHandle,
        udpRecvSocketConnection_t, socketConnectionItem
    );
    {
        XME_CHECK(XME_STATUS_SUCCESS == xme_hal_net_closeSocket(socketConnectionItem->socketHandle ), XME_STATUS_INTERNAL_ERROR);
        XME_CHECK(XME_STATUS_SUCCESS == xme_hal_net_destroySocket(socketConnectionItem->socketHandle ), XME_STATUS_INTERNAL_ERROR);
    }
    XME_HAL_TABLE_ITERATE_END();

    XME_CHECK(XME_STATUS_SUCCESS == xme_hal_sync_destroyCriticalSection(xme_wp_udp_udpRecvConfigTable.criticalSectionHandle), XME_STATUS_INTERNAL_ERROR);
    xme_wp_udp_udpRecvConfigTable.criticalSectionHandle = XME_HAL_SYNC_INVALID_CRITICAL_SECTION_HANDLE;

    XME_HAL_TABLE_FINI(xme_wp_udp_udpRecvConfigTable.table);
    XME_HAL_TABLE_FINI(xme_wp_udp_udpRecvSocketConnection_table);
    udpRecvIndex = XME_HAL_TABLE_INVALID_ROW_HANDLE;

    return XME_STATUS_SUCCESS;

}

uint16_t
xme_wp_udp_udpReceive_getPackageOverHead(void)
{
    return (uint16_t)sizeof(xme_com_packet_sample_header_t);
}
